// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <map>
#include <string>
#include <vector>

#include <boost/date_time/posix_time/posix_time_types.hpp>

#include "coin2/exchange/base/executor_util/market_ts.h"
#include "coin2/exchange/base/feed/processor.h"
#include "coin2/exchange/bitbank/symbology/product.h"

namespace coin2::exchange::bitbank::feed_v1 {
namespace impl {

using boost::posix_time::ptime;
using coin2::exchange::feed::WebsocketConfig;

class BitbankRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit BitbankRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::string> GenerateRequests(
    const std::vector<std::string>& channels,
    const std::vector<std::string>& symbols,
    const std::map<std::string, std::string>& channel_templates,
    const std::string& request_template) override {
    std::vector<std::string> reqs;

    // kline
    std::vector<std::string> ch = channels;
    std::vector<std::string> ch_kline;
    auto iter = ch.begin();
    while (iter != ch.end()) {
      if (StringStartsWith(*iter, "kline_rest")) {
        ch_kline.push_back(*iter);
        iter = ch.erase(iter);
      } else {
        iter++;
      }
    }

    if (!ch_kline.empty()) {
      static std::string day_tmp("{YYYYMMDD}");
      static std::string year_tmp("{YYYY}");

      auto timestamp = MarketTimestampProvider::GetTimestamp();
      boost::posix_time::ptime round_time = TimestampRoundHour(timestamp);
      for (auto& ch_k : ch_kline) {
        std::string c = channel_templates.at(ch_k);
        auto interval_time = GetInterval(c);
        auto max_length = GetLengthOfDay(interval_time);
        for (auto& symbol : symbols) {
          bool long_query = base::feed::RequestGenerator::CheckLongQuery(
              round_time, symbol, ch_k, &last_long_query_time_);
          std::vector<std::pair<int64_t, int64_t>> durations;
          if (long_query) {
            durations = base::feed::RequestGenerator::GetDurations(
                config_->kline_length(), config_->kline_start(), config_->kline_end(),
                interval_time, max_length);
          } else {
            // query small data
            durations = base::feed::RequestGenerator::GetDurations(
                "3", "", "",interval_time, max_length);
          }
          int idx = 0;
          // used for check duplicated dates as it is hard to remove duplicated at before steps
          std::string last_date;
          std::set<int64_t> flat_durations;
          for (const auto& d : durations) {
            flat_durations.insert(d.first);
            flat_durations.insert(d.second);
          }
          for (auto& d : flat_durations) {
            std::string c_copy = c;
            boost::replace_all(c_copy, "{}", symbol);

            std::string ymd = YmdFromTimestamp(d);
            if (c_copy.find(year_tmp) != std::string::npos) {
              ymd = ymd.substr(0, 4); 
            }

            if (last_date == ymd) {
              continue;
            }

            last_date = ymd;
            // only once tmp is existing
            boost::replace_all(c_copy, day_tmp, ymd);
            boost::replace_all(c_copy, year_tmp, ymd);

            if (long_query) {
              c_copy.append("?page=").append(std::to_string(idx));
              idx++;
            }
            reqs.emplace_back(c_copy);
          }
        }
      }
    }

    std::vector<std::string> reqs_others = RequestGenerator::GenerateRequests(
        ch, symbols, channel_templates, request_template);
    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());

    return reqs;
  }

 private:
  int64_t GetInterval(const std::string& channel) {
    if (channel.find("/1min/") != std::string::npos) {
      return (int64_t)60 * 1'000'000'000;
    } else if (channel.find("/5min/") != std::string::npos) {
      return (int64_t)300 * 1'000'000'000;
    } else if (channel.find("/15min/") != std::string::npos) {
      return (int64_t)900 * 1'000'000'000;
    } else if (channel.find("/30min/") != std::string::npos) {
      return (int64_t)1800 * 1'000'000'000;
    } else if (channel.find("/1hour/") != std::string::npos) {
      return (int64_t)3600 * 1'000'000'000;
    } else if (channel.find("/4hour/") != std::string::npos) {
      return (int64_t)4 * 3600 * 1'000'000'000;
    } else if (channel.find("/8hour/") != std::string::npos) {
      return (int64_t)8 * 3600 * 1'000'000'000;
    } else if (channel.find("/12hour/") != std::string::npos) {
      return (int64_t)12 * 3600 * 1'000'000'000;
    } else if (channel.find("/1day/") != std::string::npos) {
      return (int64_t)24 * 3600 * 1'000'000'000;
    } else if (channel.find("/1week/") != std::string::npos) {
      return (int64_t)7 * 24 * 3600 * 1'000'000'000;
    } else if (channel.find("/1month/") != std::string::npos) {
      return (int64_t)28 * 24 * 3600 * 1'000'000'000;  // the min days of a month
    } else {
      THROW() << "Unkonw new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

  int GetLengthOfDay(int64_t interval) {
    int64_t hour = (int64_t)3600 * 1'000'000'000;
    int64_t day = (int64_t)24 * 3600 * 1'000'000'000;
    if (interval <= hour) {
      return day / interval;
    } else {
      return 365;
    }
  }

  const WebsocketConfig* config_;
};

}  // namespace impl
using impl::BitbankRequestGenerator;
}  // namespace coin2::exchange::bitbank::feed_v1
